package mirrormonkey.state;

import static org.junit.Assert.assertTrue;
import mirrormonkey.state.tools.notification.entities.ServerStateUpdateEntity;
import mirrormonkey.state.tools.notification.entities.StateUpdateEntity;
import mirrormonkey.state.tools.notification.listeners.MockStateUpdateListener;
import mirrormonkey.state.tools.notification.listeners.MockStateUpdateListener.AfterInboundEvent;
import mirrormonkey.state.tools.notification.listeners.MockStateUpdateListener.AfterOutboundEvent;
import mirrormonkey.state.tools.notification.listeners.MockStateUpdateListener.BeforeInboundEvent;
import mirrormonkey.state.tools.notification.listeners.MockStateUpdateListener.BeforeOutboundEvent;
import mirrormonkey.state.tools.notification.listeners.MockStateUpdateListener.StateUpdateEvent;

import org.junit.Test;

public class ListenerNotificationTest extends SingleClientTestProto {

	public MockStateUpdateListener[] expectOutbound;

	public MockStateUpdateListener[] expectInbound;

	public MockStateUpdateListener[] expectNone;

	public class EntityData {

		ServerStateUpdateEntity serverEntity;

		StateUpdateEntity clientEntity;

	}

	public EntityData changedData;

	public EntityData unchangedData;

	public EntityData createEntity() {
		EntityData d = new EntityData();
		d.serverEntity = new ServerStateUpdateEntity();
		serverApplication.coreModule.addMapping(getFirstConnection(),
				d.serverEntity, StateUpdateEntity.class);

		sleepNoInterrupt(100);
		processEvents();

		d.clientEntity = (StateUpdateEntity) clientApplication.appState.entityProvider
				.getData(d.serverEntity.getData().id).entity;
		return d;
	}

	@Override
	public void createBoxes() {
		changedData = createEntity();
		unchangedData = createEntity();

		expectOutbound = new MockStateUpdateListener[3];
		expectOutbound[0] = changedData.serverEntity;
		expectOutbound[1] = new MockStateUpdateListener();
		serverApplication.appState.listeners.addListener(expectOutbound[1]);
		expectOutbound[2] = new MockStateUpdateListener();
		changedData.serverEntity.getData().listeners
				.addListener(expectOutbound[2]);

		expectInbound = new MockStateUpdateListener[3];
		expectInbound[0] = changedData.clientEntity;
		expectInbound[1] = new MockStateUpdateListener();
		clientApplication.appState.listeners.addListener(expectInbound[1]);
		expectInbound[2] = new MockStateUpdateListener();
		changedData.clientEntity.getData().listeners
				.addListener(expectInbound[2]);

		expectNone = new MockStateUpdateListener[4];
		expectNone[0] = unchangedData.serverEntity;
		expectNone[1] = unchangedData.clientEntity;
		expectNone[2] = new MockStateUpdateListener();
		unchangedData.clientEntity.getData().listeners
				.addListener(expectNone[2]);
		expectNone[3] = new MockStateUpdateListener();
		unchangedData.serverEntity.getData().listeners
				.addListener(expectNone[3]);
	}

	@Override
	public void removeBoxes() {
		serverApplication.coreModule.removeMapping(getFirstConnection(),
				changedData.serverEntity);
		serverApplication.coreModule.removeMapping(getFirstConnection(),
				unchangedData.serverEntity);
		sleepNoInterrupt(100);
		processEvents();
	}

	private void resetListeners() {
		resetListeners(expectOutbound);
		resetListeners(expectInbound);
		resetListeners(expectNone);
	}

	private void resetListeners(MockStateUpdateListener[] listeners) {
		for (MockStateUpdateListener l : listeners) {
			l.clear();
		}
	}

	private void expectNoEvents(MockStateUpdateListener[] expect) {
		int i = 0;
		for (MockStateUpdateListener l : expect) {
			assertTrue(l.events.size() + ":" + (i++), l.events.isEmpty());
		}
	}

	private void expectEvent(StateUpdateEvent e,
			MockStateUpdateListener[] expect) {
		for (MockStateUpdateListener l : expect) {
			l.expectEvent(e);
		}
	}

	@Test
	public void testNotification() {
		long lastTime = System.currentTimeMillis();

		int oldI = 0;
		long time = 0;
		resetListeners();
		while (time <= 30000) {
			int newI = (int) (System.currentTimeMillis() / 5000);
			long delta = System.currentTimeMillis() - lastTime;
			time += delta;
			lastTime = System.currentTimeMillis();

			if (newI != oldI) {
				oldI = newI;
				changedData.serverEntity.value++;
				sleepNoInterrupt(3000);
				processEvents();

				expectEvent(new BeforeOutboundEvent(changedData.serverEntity),
						expectOutbound);
				expectEvent(new AfterOutboundEvent(changedData.serverEntity),
						expectOutbound);
				expectEvent(new BeforeInboundEvent(clientApplication.client,
						changedData.clientEntity), expectInbound);
				expectEvent(new AfterInboundEvent(clientApplication.client,
						changedData.clientEntity), expectInbound);

				// TODO double message send workarround
				resetListeners();

				expectNoEvents(expectOutbound);
				expectNoEvents(expectInbound);
				expectNoEvents(expectNone);
			}

			sleepNoInterrupt(10);
			expectNoEvents(expectOutbound);
			expectNoEvents(expectInbound);
			expectNoEvents(expectNone);
		}
	}
}
